package org.grayrabbit.cms.plugin.descriptors;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Element;
import org.grayrabbit.cms.plugin.ModuleDescriptor;
import org.grayrabbit.cms.plugin.Plugin;
import org.grayrabbit.cms.plugin.PluginParseException;
import org.grayrabbit.cms.plugin.Resources;
import org.grayrabbit.cms.plugin.elements.ResourceDescriptor;
import org.grayrabbit.cms.plugin.elements.ResourceLocation;
import org.grayrabbit.cms.plugin.util.JavaVersionUtils;


public abstract class AbstractModuleDescriptor implements ModuleDescriptor {
	protected Plugin plugin;
	String key;
	String name;
	Class<?> moduleClass;
	String description;
	boolean enabledByDefault = true;
	boolean systemModule = false;
	protected boolean singleton = true;
	Map<String, String> params;
	private Float minJavaVersion;
	private String i18nNameKey;
	private String descriptionKey;
	private String completeKey;
	protected Resources resources = Resources.EMPTY_RESOURCES;

	  public void init(Plugin plugin, Element element) throws PluginParseException
	  {
	    this.plugin = plugin;
	    this.key = element.attributeValue("key");
	    this.name = element.attributeValue("name");
	    this.i18nNameKey = element.attributeValue("i18n-name-key");
	    this.completeKey = buildCompleteKey(plugin, this.key);
	    String clazz = element.attributeValue("class");
	    try
	    {
	      if (clazz != null)
	      {
	        this.moduleClass = plugin.loadClass(clazz, getClass());
	        try
	        {
	          Constructor<?> noargConstructor = this.moduleClass.getConstructor(new Class<?>[0]);
	          if (noargConstructor != null)
	          {
	            this.moduleClass.newInstance();
	          }
	        }
	        catch (NoSuchMethodException e)
	        {
	        }
	      }

	    }
	    catch (NoClassDefFoundError e)
	    {
	      throw new PluginParseException("Error retrieving dependency of class: " + clazz + ". Missing class: " + e.getMessage());
	    }
	    catch (UnsupportedClassVersionError e)
	    {
	      throw new PluginParseException("Class version is incompatible with current JVM: " + clazz, e);
	    }
	    catch (Throwable t)
	    {
	      throw new PluginParseException(t);
	    }

	    this.description = element.elementTextTrim("description");
	    Element descriptionElement = element.element("description");
	    this.descriptionKey = (descriptionElement != null ? descriptionElement.attributeValue("key") : null);
	    this.params = getParams(element);

	    if ("disabled".equalsIgnoreCase(element.attributeValue("state")))
	    {
	      this.enabledByDefault = false;
	    }

	    if ("true".equalsIgnoreCase(element.attributeValue("system")))
	    {
	      this.systemModule = true;
	    }

	    if (element.element("java-version") != null)
	    {
	      this.minJavaVersion = Float.valueOf(element.element("java-version").attributeValue("min"));
	    }

	    if ("false".equalsIgnoreCase(element.attributeValue("singleton")))
	    {
	      this.singleton = false;
	    }
	    else if ("true".equalsIgnoreCase(element.attributeValue("singleton")))
	    {
	      this.singleton = true;
	    }
	    else
	    {
	      this.singleton = isSingletonByDefault();
	    }

	    this.resources = Resources.fromXml(element);
	  }

	  public static Map<String,String> getParams(Element element)
	  {
	    @SuppressWarnings("unchecked")
		List<Element> elements = element.elements("param");

	    Map<String,String> params = new HashMap<String,String>(elements.size());

	    for (Element paramEl : elements)
	    {
	      String name = paramEl.attributeValue("name");
	      String value = paramEl.attributeValue("value");
	      if ((value == null) && (paramEl.getTextTrim() != null) && (!"".equals(paramEl.getTextTrim())))
	      {
	        value = paramEl.getTextTrim();
	      }
	      params.put(name, value);
	    }
	    return params;
	  }
	  
	  private String buildCompleteKey(Plugin plugin, String moduleKey)
	  {
	    if (plugin == null) {
	      return null;
	    }
	    StringBuffer completeKeyBuffer = new StringBuffer(32);
	    completeKeyBuffer.append(plugin.getKey()).append(":").append(moduleKey);
	    return completeKeyBuffer.toString();
	  }

	  public void destroy(Plugin plugin)
	  {
	  }

	  public boolean isEnabledByDefault()
	  {
	    return (this.enabledByDefault) && (satisfiesMinJavaVersion());
	  }

	  public boolean isSystemModule()
	  {
	    return this.systemModule;
	  }

	  public boolean isSingleton()
	  {
	    return this.singleton;
	  }

	  protected boolean isSingletonByDefault()
	  {
	    return true;
	  }

	  protected final void assertModuleClassImplements(Class<?> requiredModuleClazz) throws PluginParseException
	  {
	    if (!requiredModuleClazz.isAssignableFrom(getModuleClass()))
	    {
	      throw new PluginParseException("Given module class: " + getModuleClass().getName() + " does not implement " + requiredModuleClazz.getName());
	    }
	  }

	  public String getCompleteKey()
	  {
	    return this.completeKey;
	  }

	  public String getPluginKey()
	  {
	    return this.plugin.getKey();
	  }

	  public String getKey()
	  {
	    return this.key;
	  }

	  public String getName()
	  {
	    return this.name;
	  }

	  public Class<?> getModuleClass()
	  {
	    return this.moduleClass;
	  }

	  public abstract Object getModule();

	  public String getDescription() {
	    return this.description;
	  }

	  public Map<String,String> getParams()
	  {
	    return this.params;
	  }

	  public String getI18nNameKey()
	  {
	    return this.i18nNameKey;
	  }

	  public String getDescriptionKey()
	  {
	    return this.descriptionKey;
	  }

	  public List<ResourceDescriptor> getResourceDescriptors()
	  {
	    return this.resources.getResourceDescriptors();
	  }

	  public List<ResourceDescriptor> getResourceDescriptors(String type)
	  {
	    return this.resources.getResourceDescriptors(type);
	  }

	  public ResourceLocation getResourceLocation(String type, String name)
	  {
	    return this.resources.getResourceLocation(type, name);
	  }


	  public Float getMinJavaVersion()
	  {
	    return this.minJavaVersion;
	  }

	  public boolean satisfiesMinJavaVersion()
	  {
	    if (this.minJavaVersion != null)
	    {
	      return JavaVersionUtils.satisfiesMinVersion(this.minJavaVersion.floatValue());
	    }
	    return true;
	  }

	  public void setPlugin(Plugin plugin)
	  {
	    this.completeKey = buildCompleteKey(plugin, this.key);
	    this.plugin = plugin;
	  }

	  public Plugin getPlugin()
	  {
	    return this.plugin;
	  }

	  public String toString()
	  {
	    return getCompleteKey() + " (" + getDescription() + ")";
	  }
}
